/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.gct.studio.samples;

import com.android.SdkConstants;
import com.android.annotations.Nullable;
import com.android.tools.analytics.UsageTracker;
import com.android.tools.idea.observable.core.OptionalProperty;
import com.android.tools.idea.observable.core.OptionalValueProperty;
import com.android.tools.idea.wizard.model.WizardModel;
import com.appspot.gsamplesindex.samplesindex.model.Sample;
import com.google.common.base.Strings;
import com.google.wireless.android.sdk.stats.AndroidStudioEvent;
import com.google.wireless.android.sdk.stats.AndroidStudioEvent.EventKind;
import com.google.wireless.android.sdk.stats.ImportSampleEvent;
import com.intellij.ide.impl.ProjectUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
import org.jetbrains.annotations.NotNull;

/**
 * Data collected by a SampleImportWizard for a sample to download.
 */
public final class SampleModel extends WizardModel {
  private final OptionalProperty<Sample> mySample = new OptionalValueProperty<>();
  private final OptionalProperty<String> myProjectName = new OptionalValueProperty<>();
  private final OptionalProperty<File> myDir = new OptionalValueProperty<>();

  private final Consumer<String> openProjectRunnable;

  public SampleModel(Consumer<String> openProjectRunnable) {
    this.openProjectRunnable = openProjectRunnable;
  }

  public SampleModel() {
    this((basePath) -> { ProjectUtil.openOrImport(basePath, null, true); });
  }

  /**
   * Trim trailing and leading forward slashes from a string
   */
  @NotNull
  public static String trimSlashes(@NotNull String path) {
    while (path.endsWith("/")) {
      path = path.substring(0, path.length() - 1);
    }
    while (path.startsWith("/")) {
      path = path.substring(1);
    }
    return path;
  }

  private static Logger getLog() {
    return Logger.getInstance(SampleModel.class);
  }

  public OptionalProperty<Sample> sample() { return mySample; }
  public OptionalProperty<String> projectName() { return myProjectName; }
  public OptionalProperty<File> dir() { return myDir; }

  @Override
  protected void handleFinished() {
    if (!mySample.get().isPresent() || !myDir.get().isPresent() || !myProjectName.get().isPresent()) {
      getLog().error("SampleImportWizard did not collect expected information and will not complete. Please report this error.");
      return;
    }

    Sample sample = mySample.getValue();
    File dir = myDir.getValue();
    String projectName = myProjectName.getValue();

    if (!FileUtilRt.createDirectory(dir)) {
      Messages.showErrorDialog(SamplesBrowserBundle.message("create.project.dir.failed"), SamplesBrowserBundle.message("sample.import.error.title"));
      return;
    }

    String url = trimSlashes(sample.getCloneUrl());

    GithubRepoContents downloadResult = downloadSample(url);

    if (downloadResult == null) {
      return;
    }

    String basePath = myDir.getValue().getAbsolutePath();
    try {
      setGradleWrapperExecutable(new File(basePath));
    }
    catch (IOException e) {
      Messages.showWarningDialog(SamplesBrowserBundle.message("sample.import.no.gradlew.exec", projectName),
                                 SamplesBrowserBundle.message("sample.import.warning.title"));
      getLog().error(e);
    }

    // Sample ID is a string generated by us since we publish the samples to the GitHub repositories
    UsageTracker.log(AndroidStudioEvent.newBuilder()
                       .setKind(EventKind.IMPORT_SAMPLE_EVENT)
                       .setImportSampleEvent(ImportSampleEvent.newBuilder()
                                               .setImportSampleId(sample.getId())));

    openProjectRunnable.accept(basePath);
  }

  /**
   * Downloads a sample from Github.
   * @param url Github URL (e.g. "https://github.com/android/enterprise-samples")
   * @return {@link GithubRepoContents} or `null` if the download was cancelled/an error happened/the sample is empty.
   */
  @Nullable
  private GithubRepoContents downloadSample(@NotNull String url) {
    Sample sample = mySample.getValue();
    File dir = myDir.getValue();

    GithubRepoContents downloadResult = GithubRepoContents.download(url, null, null);

    if (downloadResult.isCancelled()) {
      getLog().info("Sample download cancelled by user");
      return null;
    }

    String errorMessage = downloadResult.getErrorMessage();
    if (errorMessage != null) {
      getLog().warn(errorMessage);
      Messages.showErrorDialog(errorMessage, SamplesBrowserBundle.message("sample.import.error.title"));
      return null;
    }

    List<File> sampleRoots = downloadResult.getSampleRoots();
    if (sampleRoots.isEmpty()) {
      Messages.showErrorDialog(SamplesBrowserBundle.message("git.project.dir.empty"), SamplesBrowserBundle.message("sample.import.error.title"));
      return null;
    }

    File rootFolder = downloadResult.getRootFolder();
    String basePath = dir.getAbsolutePath();
    try {
      String path = sample.getPath();
      if (!Strings.isNullOrEmpty(path)) {
        // we have a path to work with, find the project that matches it
        path = trimSlashes(path);
        sampleSearch:
        {
          for (File sampleRoot : sampleRoots) {
            if (sampleRoot.getCanonicalPath().equals(new File(rootFolder, path).getCanonicalPath())) {
              // we found our sample root
              FileUtil.copyDir(sampleRoot, new File(basePath));
              break sampleSearch;
            }
          }
          // we have a project that doesn't contain the sample root we're looking for... notify the user
          Messages
            .showErrorDialog(SamplesBrowserBundle.message("git.project.missing.sample.root", path), SamplesBrowserBundle.message("sample.import.error.title"));
          return null;
        }
      }
      else {
        // no root was specified, just grab the first root
        FileUtil.copyDir(sampleRoots.get(0), new File(basePath));
      }
    }
    catch (IOException e) {
      getLog().error(e);
      Messages.showErrorDialog(SamplesBrowserBundle.message("sample.copy.to.project.failed"), SamplesBrowserBundle.message("sample.import.error.title"));
      return null;
    }

    return downloadResult;
  }

  /**
   * Set the executable bit on the 'gradlew' wrapper script on Mac/Linux.
   * On Windows, we use a separate gradlew.bat file which does not need an executable bit.
   */
  private void setGradleWrapperExecutable(@NotNull File projectRoot) throws IOException {
    if (!SystemInfo.isUnix) {
      return;
    }
    File gradlewFile = new File(projectRoot, SdkConstants.FN_GRADLE_WRAPPER_UNIX);
    if (!gradlewFile.isFile()) {
      throw new IOException("Could not find gradle wrapper. Command line builds may not work properly.");
    }
    FileUtil.setExecutable(gradlewFile);
  }
}